package org.me.marble;

import java.util.ArrayList;
import android.os.Vibrator;
import android.content.Context;
import javax.microedition.khronos.opengles.GL10;

/**
*
* @author tungi
*/

public class MoveMarble{
	public static final int MAX_STRENGTH=1;
	
    private Path path;
    private Marble marble;
    private Vector up=new Vector(0,1,0),
                   pos=new Vector(0.0f,0.0f,0.0f),
                   //a vector with a current direction, the length mean the current speed
                   speed=new Vector(0.0f,0.0f,-1.0f),
                   last_pos=new Vector(0.0f,0.0f,0.0f),
                   direction=new Vector(0.0f,0.0f,-1.0f);

    private boolean fall=false;
    private double rotate_angle=0;
    private float time=0,angle_delta=10,distance;
    private int _angle=0,index=0,collide=-1,mistake=MAX_STRENGTH;

    public MoveMarble(Marble marble,Path path){
        this.path=path;
        this.marble=marble;        
    }

    public void transfom(GL10 gl){
        gl.glTranslatef(pos.getX(),pos.getY(),pos.getZ());
        Vector v=Vector.crossProduct(up,direction);
        gl.glRotatef(_angle,v.getX(),v.getY(),v.getZ());
    }

    public void move(GL10 gl,GameRenderer renderer,boolean endlevel){
        if(fall)
            fall();//if we left the path
        else{
            step();
            update(renderer,endlevel);
        }
        _angle+=angle_delta*speed.Length();
        _angle=_angle%360;//the angle will be less than 360 degree
    }

    public void step(){
        Vector v;
        v=Geometry.rotateVector(Math.PI/2,
                new Vector(up.getX(),0,up.getZ()),new Vector(0,1,0));
        //if 'v' not a null vector -> in this case the plane is not slant
        if(v.getX()!=0.0f || v.getY()!=0.0f || v.getZ()!=0.0f){
            v=Geometry.rotateVector(Math.PI/2,
                    new Vector(up.getX(),Math.abs(up.getY()),up.getZ()), v);
	        //v.Normalize();
	        v.Multiplication(0.05);
	        speed.Plus(v);
        }

        //rotate speed vector if the user change it
        if(rotate_angle!=0){
            v=Geometry.rotateVector(rotate_angle,speed,up);
            v.Normalize();
            Vector dir;
            if(index%2==0)
                dir=Vector.Minus(
                        Vector.Plus(path.triangles.get(index+1).getPoint(0),
                                path.triangles.get(index+1).getPoint(2)),
                        Vector.Plus(path.triangles.get(index).getPoint(2),
                                path.triangles.get(index).getPoint(0)));
            else
                dir=Vector.Minus(
                        Vector.Plus(path.triangles.get(index).getPoint(0),
                                path.triangles.get(index).getPoint(2)),
                        Vector.Plus(path.triangles.get(index-1).getPoint(2),
                                path.triangles.get(index-1).getPoint(0)));
            dir.Multiplication(0.5);
            dir.Normalize();
            double alfa=Math.acos(Geometry.getCosAngle(v,dir));
            if(alfa<Math.PI/3){//only 60 degree allowed
                float d=speed.Length()/v.Length();
                speed.set(v.getX()*d,v.getY()*d,v.getZ()*d);
            }
            rotate_angle=0;
        }

        //calculate the delta of the centre
        distance=marble.getRadius()*speed.Length()*(float)Math.PI*angle_delta/180f;
        direction=speed.getNomalizedVector();

		pos.setX(pos.getX()+distance*direction.getX());
        pos.setY(pos.getY()+distance*direction.getY());
        pos.setZ(pos.getZ()+distance*direction.getZ());
    }
    
    public void update(GameRenderer renderer,boolean endlevel){
    	if(index%2!=0)
    		index--;
    	Vibrator vibrator=(Vibrator)GameActivity.game.
        	getSystemService(Context.VIBRATOR_SERVICE);
        /*for(int i=0;i<3;++i){
            if(index==0)
                break;
            index-=path.triangles.get(index).getBackJump();
        }*/
        while(true){
            if(path.triangles.get(index).isPartProj(pos)){
                Vector new_point=Geometry.getPlaneToLineIntersection(
                            path.triangles.get(index).getNormal(),
                            path.triangles.get(index).getPoint(0),
                            new Vector(0,1,0),pos);
                
                Vector new_up=path.triangles.get(index).getNormal();
                Vector r=Vector.crossProduct(up,direction);
                r.Normalize();
                //the new direction can be calculate with the cross product of
                //the new normal and the previous "r" vector
                direction=Vector.crossProduct(r,new_up);
                direction.Normalize();
                float d=speed.Length()/direction.Length();
                speed.set(direction.getX()*d,direction.getY()*d,direction.getZ()*d);
                //change the old normal vector's coordinates
                up.set(new_up.getX(),new_up.getY(),new_up.getZ());
                pos.set(new_point.getX(),new_point.getY(),new_point.getZ());
                
                if(collide!=-1)
                	collide=-1;
                break;
            }
            //if the point is not in the triangle, maybe we left the path
            else if(!endlevel){
                //we must use the projected vector, so let's modify them
                Vector _pos=new Vector(pos.getX(),0,pos.getZ());

                Vector edge_point0=new Vector(path.triangles.get(index).getPoint(0));
                Vector edge_point1=new Vector(path.triangles.get(index).getPoint(1));
                //we need only a projection of a triangle
                edge_point0.setY(0);
                edge_point1.setY(0);

                Vector delta=Vector.Minus(_pos,last_pos);
                Vector edge=Vector.Minus(edge_point1,edge_point0);
                
                Vector mesh=Geometry.getLineToLineIntersection(last_pos,
                        edge_point0,delta,edge);

                if(mesh!=null && collide!=index
                		&& Geometry.isPartOfSection(_pos,last_pos,mesh) 
                		&& Geometry.isPartOfSection(edge_point1,edge_point0,mesh)
                        //&& !Vector.Equal(last_pos,edge_point0) 
                        //&& !Vector.Equal(last_pos,edge_point1)
                        ){
                	collide=index;
                    //we left the path                                        
                    if(OptionsLibrary.lifes==0){
                        fall=true;
                        OptionsLibrary.started=false;
                        renderer.addRunnable(timerTypes.fall.ordinal(),3000);
                        if(OptionsLibrary.vibrate)
                            vibrator.vibrate(1000);
                    }
                    else{
                        if(OptionsLibrary.vibrate)
                            vibrator.vibrate(200);
                        if(mistake==0){
                        	OptionsLibrary.lifes--;
                        	
	                    	fall=true;
	                    	mistake=MAX_STRENGTH;
	                    	renderer.addRunnable(timerTypes.fall.ordinal(),1000);
                        }
                        else{
                        	mistake--;
                        	double alfa=Math.acos(Geometry.getCosAngle(speed,
                        			Vector.Minus(path.triangles.get(index).getPoint(1),
                                			 	 path.triangles.get(index).getPoint(0))));
                        
	                        double beta=Math.acos(Geometry.getCosAngle(
	                        		Vector.Minus(path.triangles.get(index).getPoint(1),
	                        					 path.triangles.get(index).getPoint(0)),
	            					Vector.Minus(path.triangles.get((index/2)*2).getPoint(2),
	            								 path.triangles.get((index/2)*2).getPoint(0))));
	                        
	                        double gamma=0;
	                        if(beta>Math.PI/2) gamma=-2*alfa;
	                        else if(index%2==0) gamma=-alfa*(1+beta/(2*Math.PI));
	                        else gamma=(Math.PI-alfa)*(1+beta/(2*Math.PI));
	                        
	                        Vector v=Geometry.rotateVector(gamma,speed,up);
	                        float d=speed.Length()/v.Length();
	                        speed.set(v.getX()*d,v.getY()*d,v.getZ()*d);
	                        up=path.triangles.get(index).getNormal() ;
                        }
                    }                    
                    pos=Geometry.getProjectionToPlane(
                    		path.triangles.get(index).getNormal(),
                    		path.triangles.get(index).getPoint(0),mesh);
                    break;
                }
            }
            
            ArrayList<Integer> forejumps=path.triangles.get(index).getForeJump();
            android.util.Log.i("f",Integer.toString(forejumps.get(0)));
            if(forejumps.size()==1 && forejumps.get(0)==0){
            	if(OptionsLibrary.lifes==0){
                    fall=true;
                    OptionsLibrary.started=false;
                    renderer.addRunnable(timerTypes.fall.ordinal(),3000);
                    if(OptionsLibrary.vibrate)
                        vibrator.vibrate(1000);
                }
                else{              	
                	fall=true; 
                	mistake=MAX_STRENGTH;
                	OptionsLibrary.lifes--;
                	renderer.addRunnable(timerTypes.fall.ordinal(),1000);
                    if(OptionsLibrary.vibrate)
                        vibrator.vibrate(200);
                }
            	break;
            }
            else if(forejumps.size()==1)
            	index+=forejumps.get(0);
            else{
            	for(int i=0;i<forejumps.size();++i){
            		Vector _pos=new Vector(pos.getX(),0,pos.getZ());
            		Vector edge_point0=new Vector(
            				path.triangles.get(index+forejumps.get(i)).getPoint(0));
                    Vector edge_point1=new Vector(
                    		path.triangles.get(index+forejumps.get(i)).getPoint(2));
                    //we need only a projection
                    edge_point0.setY(0);
                    edge_point1.setY(0);

                    Vector delta=Vector.Minus(_pos,last_pos);
                    Vector edge=Vector.Minus(edge_point1,edge_point0);
                    
                    Vector mesh=Geometry.getLineToLineIntersection(last_pos,
                            edge_point0,delta,edge);

                    if(mesh!=null && Geometry.isPartOfSection(_pos,last_pos,mesh) 
                    		&& Geometry.isPartOfSection(edge_point1,edge_point0,mesh)){
                    	index+=forejumps.get(i);
                    	break;
                    }
            	}
            }

            //we reach the end of the path
            if(index>=path.triangles.size()){            	
            	break;
            }
        }
        last_pos.set(pos.getX(),0,pos.getZ());
    }

    public void fall(){
        //calculate the delta of the centre
        distance=(marble.getRadius()*(float)Math.PI*angle_delta/180)*(speed.Length());

        direction=speed.getNomalizedVector();

        pos.setX(pos.getX()+distance*direction.getX());
        pos.setY(pos.getY()+distance*direction.getY());
        pos.setZ(pos.getZ()+distance*direction.getZ());

        //if we left the path
        float v=marble.getRadius()*(float)Math.PI*angle_delta/180;
        float alfa=0;
        float grav=1.0f;
        float x=v*time;
        time+=0.01f;
        
        //define a throw and modify delta
        float y_delta=(float)(Math.tan(alfa)*x-grav*Math.pow(x,2)/
                (2*Math.pow(v,2)*Math.pow(Math.cos(alfa),2)));
        pos.setY(pos.getY()+y_delta);
        speed.Multiplication(0.8);
    }
    
    public void reset(){
        time=0;
        index=0;
        _angle=0;
    	fall=false;
    	collide=-1;
        rotate_angle=0;
        angle_delta=10;
    	up=new Vector(0,1,0);
    	mistake=MAX_STRENGTH;
        pos=new Vector(0.0f,0.0f,0.0f);
        speed=new Vector(0.0f,0.0f,-1.0f);
        last_pos=new Vector(0.0f,0.0f,0.0f);
        direction=new Vector(0.0f,0.0f,-1.0f);
        modifySpeed(OptionsLibrary.speedMult);
    }

/////////////////////////////////////SET METHODS////////////////////////////////
    public void modifyAngleDelta(float dt){
        //modify the angle delta with the calculated fps
        if(dt>0.00f)
            angle_delta=0.2f*dt;
    }

    public void rotate(double rotate_angle){
        if(Math.abs(this.rotate_angle+rotate_angle)<=(Math.PI/3))
            this.rotate_angle+=rotate_angle;
    }

    public void modifySpeed(double ratio){
        speed.Multiplication(ratio);
    }

//////////////////////////////////GET METHODS///////////////////////////////////
    public Vector getPos(){
        return pos;
    }

    public Vector getUp(){
        return up;
    }

    public Vector getDirection(){
        return direction;
    }

    public float getDistance(){
        return distance;
    }
    
    public int getStrength(){
    	return mistake;
    }
}
